package com.thomas.jaspersoft.studio.example.service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.thomas.jaspersoft.studio.example.request.JasperParamter;
import com.thomas.jaspersoft.studio.example.request.ReportRequest;
import com.thomas.jaspersoft.studio.util.ResourceUtil;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.util.JRSaver;

@Service
public class JasperService {
	
	private final DataSource dataSource;
	
	private final JasperExportService exportService;
	
	private final JasperParamter jasperParamter;
	
	private static final Log LOG = LogFactory.getLog(JasperService.class);
	
	@Autowired
	public JasperService(final DataSource dataSource, 
						 final JasperExportService exportService,
						 final JasperParamter jasperParamter){
		this.dataSource = dataSource;
		this.exportService = exportService;
		this.jasperParamter = jasperParamter;
	}		
	
	/**
	 * 
	 * @param reportRequest
	 * @return
	 * @throws JRException
	 * @throws SQLException
	 * @throws IOException 
	 */
	public String generateReport(ReportRequest reportRequest, Map<String, Object> parameters) throws JRException, SQLException, IOException{	
		String reportName = reportRequest.getReportName();
		String reportTemplate = this.getReportTemplate(reportName);
		String reportType = reportRequest.getReportType();
		InputStream reportTemplateInputStream  = ResourceUtil.getSingleInputStream(reportTemplate);	
		return this.generateReport(reportTemplateInputStream, parameters, reportName, reportType);
	}
	
	public String generateReport(InputStream inputStream, Map<String, Object> parameters, String reportName, String reportType) throws JRException, SQLException, IOException{	
		//the main report template will put in the folder named by the main report
		//e.g. Demo1.jrxml required to put in folder Demo1
		String parentFolderName = reportName;		
		//1 JRXML files need to be compiled so the report engine can fill them with data.
		JasperReport jasperReport = JasperCompileManager.compileReport(inputStream);		
		//To avoid compiling it every time, we can save it to a file:		
		JRSaver.saveObject(jasperReport, this.pathJasperFile(reportName));		
		
		//sub report 
		List<String> subReportNames = this.findOUtSubReports(reportName);
		if(subReportNames != null && subReportNames.size() > 0){ 
			this.compileSubReports(reportName, subReportNames);
		}
		
		//2 export the report
		JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource.getConnection());
		String reportOutputPath = this.jasperParamter.getReportOutputPath() + "/" + parentFolderName + "/" +reportName;
		String reportNameWithPath =  this.exportService.export(jasperPrint, reportOutputPath, reportType);
		
		return reportNameWithPath; 
	}
	
	private String pathJasperFile(String reportName){
		//for main report, its name is its parent folder name
		return this.pathJasperFile(reportName, reportName);
	}
	
	private String pathJasperFile(String parentFolderName, String reportName){
		String jsperParentPath = this.jasperParamter.getJasperTempFolder() + "/" + parentFolderName;
		File jsperParent = new File(jsperParentPath);
		if(!jsperParent.exists()){
			jsperParent.mkdir();
		}
		return  jsperParentPath+ "/" + reportName + ".jasper";
	}
	
	private String getReportTemplate(String reportName){
		//for main report, its name is its parent folder name
		return this.getReportTemplate(reportName, reportName);
	}
	private String getReportTemplate(String parentFolderName, String reportName){
		String reportTemplate = this.jasperParamter.getTemplatePath() + "/" + parentFolderName + "/" + reportName  +".jrxml";
		return reportTemplate;
	}
	
	/**
	 * 
	 * @param parentFolderName where the sub reports should be put in 
	 * @param parentReportName
	 * @return
	 * @throws IOException
	 */
	private List<String> findOUtSubReports(String parentReportName) throws IOException{
		String parentReportTemplate = this.getReportTemplate(parentReportName);
		Resource resource  = ResourceUtil.getSingleResourceFromPath(parentReportTemplate);
		File parentFolder = resource.getFile().getParentFile();
		List<String> subFileNames = new ArrayList<String>();
		Arrays.asList(parentFolder.list()).forEach(fileName -> 
						subFileNames.add(fileName.substring(0, fileName.lastIndexOf("."))));
		return subFileNames.stream().filter(fileName -> !parentReportName.equalsIgnoreCase(fileName))
									.collect(Collectors.toList());
	}
	
	private void compileSubReports(String parentFolderName, List<String> subReportNames) throws JRException, IOException{
		for(String subReportName : subReportNames){
			InputStream demoSubReportStream =  ResourceUtil.getSingleInputStream( 
					this.getReportTemplate(parentFolderName, subReportName));
			JRSaver.saveObject(JasperCompileManager.compileReport(demoSubReportStream), 
					this.pathJasperFile(parentFolderName, subReportName));
		}
	}
}
